package com.zry.simpleBlog.comment.aop.aspect;

import com.zry.simpleBlog.comment.aop.annotations.LogWeb;
import com.zry.simpleBlog.comment.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;

/**
 * 日志切面
 * 用切面AOP  日志信息 访问者的ip ...
 * 这里是针对注解LogAnnotation的
 * zry
 *
 * @author 14470
 */
@Slf4j
@Aspect
@Component
public class LogAspect {

    /**
     * 此处的切点是注解的方式
     * 只要出现 @LogAnnotation注解都会进入
     */
    @Pointcut("@within(com.zry.simpleBlog.comment.aop.annotations.LogWeb) || @annotation(com.zry.simpleBlog.comment.aop.annotations.LogWeb)")
    public void logPointCut() {
    }

    /**
     * 环绕增强,相当于 MethodInterceptor
     */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Object result;
        try {
            LogWeb annotation = this.getTargetAnnotation(point);
            MethodSignature signature = (MethodSignature) point.getSignature();
            // 注解上的描述
            String operation = annotation.title() + "-" + annotation.action();
            // 获取请求属性
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
            String url = request.getRequestURL().toString();
            String ip = request.getRemoteAddr();

            // 全限定类名
            String classMethod = signature.getDeclaringTypeName() + "." + signature.getName();
            Object[] args = point.getArgs();
            log.info("请求描述 : {}", operation);
            log.info("Request : {}", new RequestLog(url, ip, classMethod, args));

            long beginTime = System.currentTimeMillis();
            // 执行方法
            result = point.proceed();
            //执行时长(毫秒)  方法之后
            long time = System.currentTimeMillis() - beginTime;
            // 请求的参数
            log.info("耗时{}毫秒", time);
        } catch (Exception e) {
            if (!(e instanceof BindException || e instanceof BusinessException || e instanceof MethodArgumentNotValidException)) {
                log.error("统计日志发生异常，原因为：{}", e.getMessage(), e);
            }
            throw e;
        } catch (Throwable throwable) {
            log.error("统计日志发生异常，原因为：{}", throwable.getMessage(), throwable);
            throw throwable;
        }
        return result;
    }


    /**
     * 先去获取 Method 上的 Annotation，如果 Method 上的 Annotation 为空，则去获取 Method 上的 Annotation
     *
     * @param point
     * @return LogWeb 注解对象
     */
    private LogWeb getTargetAnnotation(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        // 获取方法注解
        Method method = signature.getMethod();
        LogWeb annotation = method.getAnnotation(LogWeb.class);
        if (annotation == null) {
            // 如果方法上的注解为空则获取类的注解
            annotation = point.getTarget().getClass().getAnnotation(LogWeb.class);
        }
        return annotation;
    }

    private static class RequestLog {
        private final String url;
        private final String ip;
        private final String classMethod;
        private final Object[] args;

        public RequestLog(String url, String ip, String classMethod, Object[] args) {
            this.url = url;
            this.ip = ip;
            this.classMethod = classMethod;
            this.args = args;
        }

        @Override
        public String toString() {
            return "{" + "url='" + url + '\'' + ", ip='" + ip + '\'' + ", classMethod='" + classMethod + '\'' + ", args=" + Arrays.toString(args) + '}';
        }
    }

}
